/**
 * \brief 使用ffmpeg将PCM音频原始raw数据编码成aac、mp3等视频流
 * \details 在播放器界面上点击录制，视频录制完成后在debug-编译目录下会有test.aac文件
 *          ffmpeg播放命令：
 *          ffplay test.aac
 * \author 将狼才鲸
 * \date 2022-04-04
 * \note 使用的FFmpeg版本：4.3.1
 * \note 版权问题，官网下载的ffmpeg库中默认没有mp3编码器！mp3编码会找不到编码器！
 *       需要自己下载第三方mp3编码器源码编译，然后再自己修改配置后重新编译ffmpeg,
 *       再使用自己编译好的ffmpeg动态库，不推荐新手尝试mp3编码！！！
 * \note 第一次编译运行会报错！注意事项：
 * 先点击“Debug”运行一次，自动创建出可执行文件输出文件夹
 * 此时“3 应用程序输出”窗口最后一行只是显示了红色的异常信息“程序异常结束。”，
 * 则表示没有把
 * ../006_AudioVideo_Codec_ffmpeg/ffmpeg-4.3.1-win64-shared/bin/
 * 下的*.dll动态库拷贝到
 * ../build-Embeded_Player-Desktop_Qt_6_2_3_MinGW_64_bit-Debug/debug/
 * 下的可执行文件目录中，拷贝完成后再点击“Debug”运行，软件才能正常工作。
 * \note .lib文件是编译时需要的，里面指定了一些动态库的链接符号，
 *       真正运行时依靠的是.dll动态库
 *
 * \note libavcodec默认通过id查找编解码器的方式都只能找到软解码器，通过名字
 *       名字查找的方式才能找到硬解码器，可以通过ffmpeg -codecs命令查看软硬件解码器的名字
 *
 * \remark 参考网址：
 *         [【FFmpeg】PCM编码成AAC](https://www.jianshu.com/p/2195c757ffff)
 */

/**** 头文件 ****/
#include "module.h"
#include <string.h> // memcpy
#include <stdio.h>  // printf fflush

#include "libavcodec/avcodec.h"   // 音频、视频、字幕编解码
#include <libswresample/swresample.h> // 音频重采样

/**** 宏定义 ****/
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define pr_info printf
#define pr_err printf
#define MDATA_LEN (256 * 1024)

// 如果从文件中读取PCM则打开（测试用），否则从麦克风读取
//#define DIRECT_READ_FROM_FILE

/**** 全局变量（供别的模块调用） ****/
int audiomdatalen = 0; // 既作为长度，又作为消费者生产者的锁

/**** 模块内私有变量 ****/
// 本模块缓存数据用（以后会修改软件架构，替换成缓存队列）
static unsigned char inmdata[MDATA_LEN]; // 输入缓存
static unsigned char audio_mdata[MDATA_LEN]; // 转换后的缓存
static unsigned char encodeddata[MDATA_LEN]; // 编码后的数据
// 目的模块（向它发送数据）
static struct module *target = NULL;
static int av_type; // 3: mp3, 4: aac, 5: ac3

/* ffmpeg libavcodec解码需要的一些变量 */
static AVCodecContext *pCodecCtx = NULL;    // 输出编码器要用到的数据主结构体
static AVCodec *pCodec = NULL;              // 输出编码器
static AVFrame *pS16Frame = NULL;           // 编码时的每个输入帧
static AVFrame *pFltpFrame = NULL;          // 音频输入帧重采样转换格式
static SwrContext *pSwrCtx = NULL;          // 转换PCM输入数据的格式
static AVPacket *pPacket = NULL;            // 编码后的每个已编码帧
static int pts = 0;                         // 显示时间戳
static int inputmdatalen = 0;               // PCM格式转换后的长度
static int tick;    // 单位：ms
static int tick_start_value;
static int tick_start_flag = 0;

#ifdef DIRECT_READ_FROM_FILE
static FILE *fp_pcm;
static unsigned char pcm_buffer[MDATA_LEN]; // 存储变换格式后的PCM
static int pcm_buffer_size = 0;
#endif

/**** 模块内私有函数 ****/
/**
 * \brief 编码器检查是否支持此PCM格式
 */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{
    const enum AVSampleFormat *p = codec->sample_fmts;

    while (*p != AV_SAMPLE_FMT_NONE) {
        if (*p == sample_fmt)
            return 1;
        p++;
    }
    return 0;
}

/**
 * \brief 获取编码器支持的最高音频采样率
 * \details 用于检查编码器最好是否支持此采样率
 */
static int select_sample_rate(const AVCodec *codec)
{
    const int *p;
    int best_samplerate = 0;

    if (!codec->supported_samplerates)
        return 44100;

    p = codec->supported_samplerates;
    while (*p) {
        if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
            best_samplerate = *p;
        p++;
    }
    return best_samplerate;
}

/**
 * \brief 获取编码器最高支持的通道数
 */
static int select_channel_layout(const AVCodec *codec)
{
    const uint64_t *p;
    uint64_t best_ch_layout = 0;
    int best_nb_channels   = 0;

    if (!codec->channel_layouts)
        return AV_CH_LAYOUT_STEREO;

    p = codec->channel_layouts;
    while (*p) {
        int nb_channels = av_get_channel_layout_nb_channels(*p);

        if (nb_channels > best_nb_channels) {
            best_ch_layout    = *p;
            best_nb_channels = nb_channels;
        }
        p++;
    }
    return best_ch_layout;
}

/**
 * \brief开始编码
 */
static int codec_start(void)
{
    int ret;

#ifdef DIRECT_READ_FROM_FILE
    fp_pcm = fopen("../004_MultiMedia_VideoAudio/002_pcm_audio_play/002_pcm_audio_play.pcm", "rb+");
#endif

    /* 1. 查找编码器 */
    /**
     * \brief 使用名字查找时才能找到硬解码器，使用id查找时只支持软解码
     * \details 查看所有软硬件解码器名称：ffmpeg -codecs
     * DEAIL. aac    AAC (Advanced Audio Coding) (decoders: aac aac_fixed )
     * D.AIL. aac_latm    AAC LATM (Advanced Audio Coding LATM syntax)
     * DEAIL. ac3    ATSC A/52A (AC-3) (decoders: ac3 ac3_fixed ) (encoders: ac3 ac3_fixed )
     * DEAIL. mp2    MP2 (MPEG audio layer 2) (decoders: mp2 mp2float ) (encoders: mp2 mp2fixed libtwolame )
     * DEAIL. mp3    MP3 (MPEG audio layer 3) (decoders: mp3float mp3 ) (encoders: libmp3lame libshine )
     * 以上都是软解码器libmp3lame和libshine要安装开源第三方库
     */
    enum AVCodecID codec_id;
    if (av_type == 3)
        codec_id = AV_CODEC_ID_MP3;
    else // video_type == 4
        codec_id = AV_CODEC_ID_AAC;
    pCodec = avcodec_find_encoder(codec_id); // 按id查找

    //char *encoder_name;
    //if (av_type == 3)
    //    encoder_name = "libmp3lame"; // mp3需要编译第三方库，重新配置编译ffmpeg
    //else // video_type == 4
    //    encoder_name = "aac";
    //pCodec = avcodec_find_encoder_by_name("libmp3lame"); // 按名字查找

    /* 2. 配置编码器 */
    pCodecCtx = avcodec_alloc_context3(pCodec);
    pCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    if (!check_sample_fmt(pCodec, pCodecCtx->sample_fmt)) {
        pr_err("Encoder does not support sample format %s\n",
                av_get_sample_fmt_name(pCodecCtx->sample_fmt));
        return -EPERM;
    }
    pCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;//select_channel_layout(pCodec);
    pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);
    pCodecCtx->sample_rate = 44100;//select_sample_rate(pCodec);
    pCodecCtx->bit_rate = 64000;

    /* 3. 打开编码器 */
    ret = avcodec_open2(pCodecCtx, pCodec, NULL);
    if (ret < 0) {
        pr_err("avcodec_open2 fail\n");
        goto err1;
    }

    /* 4. 转换原始帧格式：s16 -> fltp */
    // aac编码只支持fltp格式
    pS16Frame = av_frame_alloc(); // 用于转换的中介帧
    if (!pS16Frame) {
        pr_err("av_frame_alloc fail\n");
        goto err1;
    }
    //TODO: 编码器默认是1024，需要和摄像头传入的每帧采样点一致，最好能有参数传入的接口
    pS16Frame->nb_samples = 1024;//pCodecCtx->frame_size;
    //pS16Frame->nb_samples = 4096;//pCodecCtx->frame_size;
    pS16Frame->format = AV_SAMPLE_FMT_S16;
    pS16Frame->channel_layout = AV_CH_LAYOUT_STEREO;
    pS16Frame->sample_rate = 44100;
    //pS16Frame->channels = av_get_channel_layout_nb_channels(pS16Frame->channel_layout);
    // 按需要的帧长度重新分配内存
    ret = av_frame_get_buffer(pS16Frame, 0);
    if (ret < 0) {
        pr_err("av_frame_get_buffer fail \n");
        goto err2;
    }

    // 重采样，设置PCM格式转换参数
    pSwrCtx = swr_alloc_set_opts(NULL, pCodecCtx->channel_layout,
              pCodecCtx->sample_fmt, pCodecCtx->sample_rate,
              pS16Frame->channel_layout, pS16Frame->format,
              pS16Frame->sample_rate, 0, NULL);
    if (!pSwrCtx) {
        pr_err("swr_alloc_set_opts fail\n");
        goto err2;
    }
    ret = swr_init(pSwrCtx);
    if (ret < 0) {
        pr_err("swr_init fail\n");
        goto err2;
    }
    pFltpFrame = av_frame_alloc(); // 申请输入帧内存
    //TODO: 编码器默认是1024，需要和摄像头传入的每帧采样点一致，最好能有参数传入的接口
    pFltpFrame->nb_samples = 1024;//pCodecCtx->frame_size;
    //pFltpFrame->nb_samples = 4096;//pCodecCtx->frame_size;
    pFltpFrame->format = pCodecCtx->sample_fmt;
    pFltpFrame->channel_layout = pCodecCtx->channel_layout;
    pFltpFrame->sample_rate = pCodecCtx->sample_rate;
    //pFltpFrame->channels = av_get_channel_layout_nb_channels(pS16Frame->channel_layout);
    // 按需要的帧长度重新分配内存
    ret = av_frame_get_buffer(pFltpFrame, 0);
    if (ret < 0) {
        pr_err("av_frame_get_buffer fail\n");
        goto err3;
    }
#ifdef DIRECT_READ_FROM_FILE
    pcm_buffer_size = pS16Frame->nb_samples * av_get_bytes_per_sample(pS16Frame->format)
                  * pS16Frame->channels;
#endif
    // 分配音频包的内存，供每帧编码输出使用
    pPacket = av_packet_alloc();
    if (!pPacket) {
        pr_err("av_packet_alloc fail\n");
        goto err3;
    }

    return 0;

err3:
    if (pFltpFrame)
        av_frame_free(&pFltpFrame);
err2:
    if (pS16Frame)
        av_frame_free(&pS16Frame);
err1:
    if (pCodecCtx)
        avcodec_close(pCodecCtx);

    return -EPERM;
}

// 不使用AVFormat时，需要自行生成AAC的头信息，否则播放器无法播放，
// 也无法封装进MP4容器
static void get_adts_header(AVCodecContext *ctx, uint8_t *adts_header, int aac_length)
{
    uint8_t freq_idx = 0; // 0: 96000 Hz, 3: 48000 Hz, 4: 44100 Hz
    switch (ctx->sample_rate) {
        case 96000: freq_idx = 0; break;
        case 88200: freq_idx = 1; break;
        case 64000: freq_idx = 2; break;
        case 48000: freq_idx = 3; break;
        case 44100: freq_idx = 4; break;
        case 32000: freq_idx = 5; break;
        case 24000: freq_idx = 6; break;
        case 22050: freq_idx = 7; break;
        case 16000: freq_idx = 8; break;
        case 12000: freq_idx = 9; break;
        case 11025: freq_idx = 10; break;
        case 8000: freq_idx = 11; break;
        case 7350: freq_idx = 12; break;
        default: freq_idx = 4; break;
    }
    uint8_t chanCfg = ctx->channels;
    uint32_t frame_length = aac_length + 7;
    adts_header[0] = 0xFF;
    adts_header[1] = 0xF1;
    adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);
    adts_header[3] = (((chanCfg & 3) << 6) + (frame_length  >> 11));
    adts_header[4] = ((frame_length & 0x7FF) >> 3);
    adts_header[5] = (((frame_length & 7) << 5) + 0x1F);
    adts_header[6] = 0xFC;
}

extern int mp4vmdatalen;

char privdata[64];
int privdata_len = 0;

/* 获取到一帧编码包前会阻塞 */
static int codec_parse(void)
{
    int ret;
    char *indata = inmdata;
    size_t size = audiomdatalen; // Qt麦克风输入默认7057，编码器默认1024*2*2

#ifdef DIRECT_READ_FROM_FILE
    if (feof(fp_pcm) != 0)
        return 0;
    indata = pcm_buffer;
    size = fread(indata, 1, pcm_buffer_size, fp_pcm);
    if (size == 0) {
        pr_err("fread fail\n");
        return -EPERM;
    }
#endif

    // 获取每帧的采样点数
    int nb_samples = size / (av_get_bytes_per_sample(pS16Frame->format) * pS16Frame->channels);
    pS16Frame->nb_samples = nb_samples;
    pFltpFrame->nb_samples = nb_samples;

    av_samples_fill_arrays(pS16Frame->data, pS16Frame->linesize,
            indata,
			pS16Frame->channels, pS16Frame->nb_samples,
            pS16Frame->format, 0);

    // PCM格式转换
    ret = swr_convert(pSwrCtx, pFltpFrame->data, pFltpFrame->nb_samples,
                      pS16Frame->data, pS16Frame->nb_samples);

    // pts时间基是采样率
    // 时间：  1s    1000ms Nms(normaly 23ms/per packet)
    // 采样点：44100 44100  nb_samples
    // 时间基：44100 44100  pts
#if 1   /* 一般录制时，音频帧的持续时间都是一致的 */
    pts += pFltpFrame->nb_samples;
    pFltpFrame->pts = pts;
#else
    //TODO: 将音频帧pts按每帧采样点计算的方式移动到最开始的模块去
    //TODO：判断从tick获取的pts和计算数量量得出的pts会不会偏差越来越大，
    //      如果是，说明音频会丢包或者卡顿，实时性不好，可以加上打印或者日志提醒
    int pts_from_tick = (((int64_t)tick - tick_start_value) * 44100) / 1000;    /* pts以44100为时间基 */
#endif

    // 向解码器发送一帧数据
    ret = avcodec_send_frame(pCodecCtx, pFltpFrame);
    if (ret < 0) {
        pr_err("avcodec_send_frame fail\n");
        return -ENOENT;
    }
    // 获取解码完的一帧，获取到了就退出
    while (1) {
        ret = avcodec_receive_packet(pCodecCtx, pPacket);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            pr_err("avcodec_receive_packet fail\n");
            return -EPERM;
        }

        if (privdata_len == 0) {
            privdata_len = pCodecCtx->extradata_size;
            memcpy(privdata, pCodecCtx->extradata, privdata_len);
        }

        uint8_t aac_header[7];
        get_adts_header(pCodecCtx, aac_header, pPacket->size);
        memcpy(encodeddata, &pPacket->pts, sizeof(int64_t));
        memcpy(encodeddata + sizeof(int64_t), aac_header, 7);
        memcpy(encodeddata + sizeof(int64_t) + 7, pPacket->data, pPacket->size);

        // 将已编码包发送给下一个模块进行处理
//         while (mp4amdatalen) {}
         if (target)
             target->putdata(encodeddata, pPacket->size + sizeof(int64_t) + 7, 2);

        av_packet_unref(pPacket);
    }

    return 0;
}

static int flush_encoder(void)
{
    int ret;

    // 清空输入缓存
    ret = avcodec_send_frame(pCodecCtx, NULL);
    if (ret < 0) {
        printf("avcodec_send_frame fail \n");
        return -EPERM;
    }
    while (1) {
        ret = avcodec_receive_packet(pCodecCtx, pPacket);
        if (ret == AVERROR(EINVAL) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            pr_err("avcodec_receive_packet fail\n");
            break;
        }

        // 将已编码包发送给下一个模块进行处理
//         while (mp4amdatalen) {}
         if (target) {
             uint8_t aac_header[7];
             get_adts_header(pCodecCtx, aac_header, pPacket->size);
             memcpy(encodeddata, aac_header, 7);
             memcpy(encodeddata + 7, pPacket->data, pPacket->size);

             target->putdata(encodeddata, pPacket->size + 7, 2);
         }

        av_packet_unref(pPacket);
    }

    return ret;
}

static int codec_stop(void)
{
    // 写入剩余帧数据（可能没有）
    flush_encoder();

    // 释放内存
    if (pCodecCtx)
        avcodec_close(pCodecCtx);
    if (pS16Frame)
        av_frame_free(&pS16Frame);
    if (pFltpFrame)
        av_frame_free(&pFltpFrame);
    if (pPacket)
        av_packet_free(&pPacket);

    return 0;
}

/**** 模块成员函数（供别人通过函数指针调用） ****/
static int module_init(void)
{
    if (target)
        target->init();

    return 0;
}

static int module_start(void *param)
{
    start_param_t sparam;
    start_param_t *isparam = param;

    if (isparam == NULL)
        return -EPERM;

    audiomdatalen = 0;

    if (isparam->av_type == AVTYPE_H264) {
        sparam.file_name = "test.h264";
        sparam.av_type = AVTYPE_H264;
        av_type = 1;
    } else if (isparam->av_type == AVTYPE_HEVC) {
        sparam.file_name = "test.hevc";
        sparam.av_type = AVTYPE_HEVC;
        av_type = 2;
    } else if (isparam->av_type == AVTYPE_MP3) {
        sparam.file_name = "test.mp3";
        sparam.av_type = AVTYPE_MP3;
        av_type = 3;
    } else if (isparam->av_type == AVTYPE_AAC) {
        sparam.file_name = "test.aac";
        sparam.av_type = AVTYPE_AAC;
        av_type = 4;
    } else if (isparam->av_type == AVTYPE_AC3) {
        sparam.file_name = "test.ac3";
        sparam.av_type = AVTYPE_AC3;
        av_type = 5;
    }

    if (target) {
        target->start(&sparam);
    }

    codec_start();

    return 0;
}

static int module_run(void)
{
    int ret;

    if (target)
        target->run();

    if (!audiomdatalen) // 如果没有数据，未获取到锁
        return 0;

    ret = codec_parse();

    if (ret != -ENOENT)
        audiomdatalen = 0; // 释放锁

    return 0;
}

static int module_stop(void)
{
    if (target)
        target->stop();

    codec_stop();

    return 0;
}

static int module_exit(void)
{
    if (target)
        target->exit();

    return 0;
}

static int module_settarget(void *mtarget)
{
    target = (struct module *)mtarget;

    return 0;
}

static int module_putdata(void *data, int len, int flag)
{
    if (audiomdatalen) // 如果有数据，未获取到锁
        return 0;
    if (!data || len > MDATA_LEN)
        return 0;

    memcpy(&tick, data, sizeof(int));
    if (!tick_start_flag) {
        /* 如果是录制后第一次获取音频帧 */
        tick_start_value = tick;
        tick_start_flag = 1;
    }
    memcpy(inmdata, data + sizeof(int), len - sizeof(int));
    audiomdatalen = len - sizeof(int);

    return 0;
}

struct module g_module_audioenc = {
    "audio encode", module_init,
    module_start, module_run,
    module_stop, module_exit,
    module_settarget, module_putdata
};
